<?php

	namespace Aspic;
	
	use Aspic as A;
	
/*

	Comment ça fonctionne ?
	
		- Une validation n'est effectuée que si le champ est "required" ou que celui-ci est rempli.
		Dans le cas contraire, les données étant vide et l'utilisateur pouvant agir ainsi, on considère que 
		la validation n'a pas à être prise en compte puisque le champ est optionnel.
		
		Pour la validation equalTo par exemple, elle ne sera considérée que si le champ contient quelque chose
		et aucune erreur ne sera retournée dans ce cas sauf si le champ est mis à "required"
		
		addValidation(nomDuChamp,typeDeValidation,messageSiErreur,paramètres, condition)
		
		condition is a php function that takes 2 args ($fieldValue, $fieldsArray) and have to return true to evaluate the validation
		
		Error message could be set to false to set no error message
*/
	
	/**
	* Form validation class
	* 
	* @Author Mallory Dessaintes <mdessaintes@gmail.com>
	* @version 1.0
	*/
	class FormValidation {
		protected $_arr_formFields;
		
		protected $_arr_validations;
		protected $_arr_arrayValidations;
		protected $_arr_fileValidations;
		// protected $_arr_customValidations;
		
		protected $_arr_errors;
		
		protected $_errors_startWith;
		protected $_errors_endWith;
		protected $_errors_html_separator;
		
		protected $_startFunctions;
		
		public function __construct() {
			$this->_arr_errors = array();
			
			$this->_arr_validations = array();
			$this->_arr_arrayValidations = array();
			$this->_arr_fileValidations = array();
			// $this->_arr_customValidations = array();
			
			$this->_startFunctions = array();
			
			$this->setErrorsDisplay('<p style="color: red">','</p>','');
		}
		
		protected function validationConditionPass($condition, $value, $values) {
			if($condition === null) {
				return true;
			}
			elseif(is_callable($condition)) {
				return $condition($value, $values);
			}
			else {
				throw new Exception(Util::var2String($condition).' is not callable');
			}
		}
		
		/**
		* Add a function that will be called when the validate process has just started
		* It allows adding validation base on special conditions
		* ex: 
		* if user choose an existing image for his avatar,  we add validation to test if the avatar really exists
		* if user want to upload his own, we add validation to test the weight and size of it
		* 
		* That's usefull when validation rules depend on form data
		* 
		* @param function $func Function that will be called
		* args: 
		*  $fields => Array $fields to validate
		*  $validationObject => The FormValidation object
		* 
		* @drepecated use $validation $condition argument instead
		*/
		public function addValidationStartFunction($func) {
			$this->_startFunctions[] = $func;
		}
		
		public function addValidation($fieldName, $validationType, $errorMessage = null, $params = array(), $condition = null) {
			if(method_exists($this,'validation_'.$validationType)) {
				$validation = array(
						'fieldName' => $fieldName,
						'validationType' => $validationType,
						'arr_params' => (is_array($params)?$params:explode(';', $params)),
						'errorMessage' => $errorMessage, 
						'condition' => $condition
					);
			
				// Is it necessary to placed required validations on the top ?
				// No because others validations will not be tested if the field is empty (considered as optional)
				if($validationType == 'required') {
					array_unshift($this->_arr_validations, $validation);
				}
				else {
					$this->_arr_validations[] = $validation;
				}
				
				return $this;
			}
			else {
				trigger_error('validation type ('.$validationType.') does not exists',E_USER_ERROR);
			
				return false;
			}
		}
		
		public function addArrayValidation($fieldName, $validationType, $errorMessage = null, $params = array(), $condition = null) {
			if(method_exists($this,'validation_'.$validationType)) {
				$validation = array(
						'fieldName' => $fieldName,
						'validationType' => $validationType,
						'arr_params' => (is_array($params)?$params:explode(';', $params)),
						'errorMessage' => $errorMessage, 
						'condition' => $condition
					);
			
				// Is it necessary to placed required validations on the top ?
				if($validationType == 'required') {
					array_unshift($this->_arr_arrayValidations, $validation);
				}
				else {
					$this->_arr_arrayValidations[] = $validation;
				}
				
				return $this;
			}
			else {
				trigger_error('validation type ('.$validationType.') does not exists',E_USER_ERROR);
			
				return false;
			}
		}
		
		public function addFileValidation($fieldName, $validationType, $errorMessage = null, $params = array(), $condition = null) {
			if(method_exists($this,'validation_file_'.$validationType)) {
				$validation = array(
						'fieldName' => $fieldName,
						'validationType' => $validationType,
						'arr_params' => (is_array($params)?$params:explode(';',$params)),
						'errorMessage' => $errorMessage, 
						'condition' => $condition
					);
			
				if($validationType == 'required') {
					array_unshift($this->_arr_fileValidations,$validation);
				}
				else {
					$this->_arr_fileValidations[] = $validation;
				}
				
				return $this;
			}
			else {
				trigger_error('validation type ('.$validationType.') does not exists',E_USER_ERROR);
			
				return false;
			}
		}

		/**
		* Add a custom validation for the form
		* 
		* @param string $fieldName Here the field name is use to associate the error with a field 
		* (but the all the form fields are given to the custom function)
		* @param func $func A function that take the fields array (All fields sended by the form) as argument
		* and return true if all is correct or false if it is not
		* @param string $errorMessage The error message added if the validation failed
		*/
		/*
		public function addCustomValidation($fieldName, $errorMessage, $func) {
			$this->_arr_customValidations[] = array(
				'fieldName' => $fieldName,
				'function' => $func,
				'errorMessage' => $errorMessage
			);
		}
		*/
		
		public function __invoke() {
			return call_user_func_array(array($this, 'addValidation'), func_get_args());
		}
		
		/**
		* Test is a form is valid by checking set validations
		* 
		* @param array $array The array containing the field in their keys ($form['fieldName'])
		* 
		* @param array $returnErrors Set if the method return directly the errors or only a boolean 
		* which indicate if validation has succeded
		* 
		* @return if $returnErrors = true, an array with errors, in others case, true if the form is valid
		*/
		public function isFormValid($array = null, $returnErrors = false) {
			if($array == null) {
				if($_SERVER['REQUEST_METHOD'] == 'GET') {
					$this->_arr_formFields = $_GET;
				}
				else { // ($_SERVER['REQUEST_METHOD'] == 'POST') {
					$this->_arr_formFields = $_POST;
				}
			}
			else {
				$this->_arr_formFields = $array;
			}
			
			if(count($this->_startFunctions)) {
				foreach($this->_startFunctions as $func) {
					call_user_func($func, $this->_arr_formFields, $this);
				}
			}
			
			$this->_arr_errors = array();
			
			# NORMAL VALIDATION
			
			foreach($this->_arr_validations as $validation) {
				// This is a reference, so could be not set
				$currValue = &UArray::getSubArrayRefFromHtmlPath($this->_arr_formFields, $validation['fieldName']);
			
				if($this->validationConditionPass($validation['condition'], $currValue, $this->_arr_formFields)) {
					$funcName = 'validation_'.$validation['validationType'];
					
					
					if(!isset($currValue)) {
						$currValue = '';
					}
					
					# REQUIRED VALIDATION
					
					// We do not valid when the field is empty, we consider that it is an optionnal field
					// (or a required validation was set on it and will be showned)
					if($validation['validationType'] == 'required' OR $currValue != '') {
							if(!
								$this->validateField(
									$currValue, 
									$funcName, 
									$validation['arr_params']
								)
							) {
								$this->addError(
									$validation['fieldName'], 
									$currValue,
									$validation['validationType'], 
									$validation['arr_params'], 
									$validation['errorMessage']
								);
							}
					}
					
					}
			}
			
			unset($currValue);
			
			# ARRAY VALIDATION
			
			foreach($this->_arr_arrayValidations as $validation) {
				// This is a reference, so could be not set
				$currValue = &UArray::getSubArrayRefFromHtmlPath($this->_arr_formFields, $validation['fieldName']);
				
				if($this->validationConditionPass($validation['condition'], $currValue, $this->_arr_formFields)) {
					$funcName = 'validation_'.$validation['validationType'];
					
					if(isset($currValue)) {
						if(!is_array($currValue)) {
							$this->_arr_formFields[$validation['fieldName']] = array();
						}
						if(!
							$this->validateArray(
								$currValue,
								$funcName, 
								$validation['arr_params']
							)
						) {
							$this->addError(
								$validation['fieldName'], 
								$currValue,
								$validation['validationType'], 
								$validation['arr_params'], 
								$validation['errorMessage']
							);
						}
					}
				}
			}
			
			unset($currValue);
			
			### FILES ###
			
			foreach($this->_arr_fileValidations as $file) {
				// This is a reference, so could be not set
				$currFile = &UArray::getSubArrayRefFromHtmlPath($_FILES, $file['fieldName']);
				
				if($this->validationConditionPass($validation['condition'], $currFile, $this->_arr_formFields)) {
					if(!isset($currFile)) {
						$currFile = null;
					}
					// else {
						// $currFile = (object)$currFile;
					// }
					
					$funcName = 'validation_file_'.$file['validationType'];

					if($file['validationType'] == 'required' OR ($currFile != null AND $currFile['name']  != '')) {
						if(!
							$this->validateFile(
								$currFile, 
								$funcName, 
								$file['arr_params']
							)
						) {
							$this->addError(
								$file['fieldName'], 
								$currFile,
								$file['validationType'], 
								$file['arr_params'], 
								$file['errorMessage']
							);
						}
					}
				}
			}
			
			
			### CUSTOM ###
			
			// foreach($this->_arr_customValidations as $custom) {
				// if(!
					// $this->validateCustom(
						// $custom['fieldName'], 
						// $custom['function'], 
						// $custom['errorMessage']
					// )
				// ) {
					// $this->addUserError(
						// $fieldName, 
						// $file['errorMessage']
					// );
				// }
			// }
			
			if($returnErrors) {
				return $this->_arr_errors;
			}
			else {
				return (count($this->_arr_errors)?false:true);
			}
		}
		
		public function isValid() {
			return call_user_func_array(array($this, 'isFormValid'), func_get_args());
		}
		
		/**
		* Return the errors in the last validated form (isFormValid method)
		*/
		public function getErrors() {
			return $this->_arr_errors;
		}
		
		/**
		* Use to add a user error (for example if you want to add a "email already exists" validation)
		*/
		public function addUserError($fieldName, $errorMessage) {
			$ref = &UArray::getSubArrayRefFromHtmlPath($this->_arr_errors, $fieldName);
			$ref[] = $errorMessage;
		}
		
		/**
		* Show errors in html
		*/
		public function getErrorsHtml($fieldName, $startWith = null, $endWith = null, $html_separator = null) {
			if(isset($this->_arr_errors[$fieldName])) {
				if($startWith == null)
					$startWith = $this->_errors_startWith;
				if($endWith == null)
					$endWith = $this->_errors_endWith;
				if($html_separator == null)
					$html_separator = $this->_errors_html_separator;
				
				$str = '';
			
				foreach($this->_arr_errors[$fieldName] as $errorStr) {
					$str .= $startWith;
					$str .= $errorStr;
					$str .= $endWith.$html_separator;
				}

				if(strlen($html_separator))
					$str = substr($str,0,-strlen($html_separator));
				
				return $str;
			}
		}
		
		/**
		* Set error display type
		*/
		public function setErrorsDisplay($startWith,$endWith,$html_separator) {
			$this->_errors_startWith = $startWith;
			$this->_errors_endWith = $endWith;
			$this->_errors_html_separator = $html_separator;
		}
		
		/**
		* Validate any field from the array given for validation
		*/
		protected function validateField($fieldValue, $funcName, $arr_params) {
			if(method_exists($this, $funcName)) {
				$params = array_merge(array($fieldValue), $arr_params);
				
				return call_user_func_array(array($this, $funcName), $params);
			}
		}
		
		/**
		* Validate a file
		*/
		protected function validateFile($fieldValue, $funcName, $arr_params) {
			if(method_exists($this, $funcName)){
				$params = array_merge(array($fieldValue), $arr_params);
				// var_dump($_FILES);
				// var_dump($_POST);
				return call_user_func_array(array($this, $funcName), $params);
			}
		}
		
		/**
		* Validate all items (recursively) of an array with a validation. Return true if validation pass OR if fieldValue is not an array
		*/
		protected function validateArray($fieldValue, $funcName, $arr_params) {
			$valid = true;
			
			if(!is_array($fieldValue)) {
				// throw new Exception('Try array validation on other than array ('.print_r($fieldValue, true).')');
				return true;
			}
		
			foreach($fieldValue as $v) {
				if(is_array($v)) {
					$valid = $valid && validateArray($v, $funcName, $arr_params);
				}
				else {
					$params = array_merge(array($v), $arr_params);
					
					$valid = $valid && call_user_func_array(array($this, $funcName), $params);
				}
			}
			
			return $valid;
		}
		
		protected function getDefaultErrorMessage($fieldName, $validationType, $fieldValue, $arr_params) {
			// return '"'.$fieldName.'" is not valid ('.$validationType.') with "'.$fieldValue.'", params : ('.var_export($arr_params,true).')';
			return '"'.$fieldName.'" is not valid ('.$validationType.') with "'.$fieldValue.'"';
		}
		
		protected function addError($fieldName, $fieldValue, $validationType, $arr_params, $errorMessage) {
			if($errorMessage === null) {
				$errorMessage = $this->getDefaultErrorMessage($fieldName, $validationType, $fieldValue, $arr_params);
			}
			
			$ref = &UArray::getSubArrayRefFromHtmlPath($this->_arr_errors, $fieldName);
			$ref[] = A\UString::fromArgsArray($errorMessage, $arr_params);
			
			// $this->_arr_errors[$fieldName][] = A\UString::fromArgsArray($errorMessage, $arr_params);
		}
		
		# VALIDATION FUNCTIONS
		
		protected function validation_required($fieldValue) {
			// var_dump( !((is_array($fieldValue) AND !count($fieldValue)) OR $fieldValue == ''));
			return !((is_array($fieldValue) AND !count($fieldValue)) OR $fieldValue == '');
		}
		
		protected function validation_custom($fieldValue, $func) {
			return $func($fieldValue, $this->_arr_formFields);
		}
		
		protected function validation_email($fieldValue) {
			return Validator::isEmail($fieldValue);
		}
		
		protected function validation_alpha($fieldValue) {
			return Validator::isAlpha($fieldValue);
		}
		
		protected function validation_alpha2($fieldValue) {
			return Validator::isAlpha2($fieldValue);
		}
		
		protected function validation_alnum($fieldValue) {
			return Validator::isAlnum($fieldValue);
		}
		
		protected function validation_alnum2($fieldValue) {
			return Validator::isAlnum2($fieldValue);
		}
		
		protected function validation_digit($fieldValue) {
			return Validator::isDigit($fieldValue);
		}
		
		protected function validation_numeric($fieldValue) {
			return Validator::isNumeric($fieldValue);
		}
		
		protected function validation_lessThan($fieldValue, $value) {
			return Validator::isLessThan($fieldValue);
		}
		
		protected function validation_greaterThan($fieldValue, $value) {
			return Validator::isGreatherThan($fieldValue);
		}
		
		// This function only works when $fieldValue is not empty
		protected function validation_equalTo($fieldValue, $fieldEqualName) {
			$fieldValue = (is_array($fieldValue)?null:$fieldValue);
		
			if(isset($this->_arr_formFields[$fieldEqualName]) AND $fieldValue == $this->_arr_formFields[$fieldEqualName])
				return true;
			else
				return false;
		}
		
		// This function only works when $fieldValue is not empty
		protected function validation_notEqualTo($fieldValue, $fieldNotEqualName) {
			$fieldValue = (is_array($fieldValue)?null:$fieldValue);
		
			if(isset($this->_arr_formFields[$fieldNotEqualName])) {
				if($fieldValue != $this->_arr_formFields[$fieldNotEqualName]) {
					return true;
				}
				else {
					return false;
				}
			}
			else
				return true;
		}
		
		protected function validation_equalToValue($fieldValue, $value) {
			return ($fieldValue == $value);
		}
		
		protected function validation_notEqualToValue($fieldValue, $value) {
			return ($fieldValue != $value);
		}
		
		protected function validation_token($fieldValue, Security\Token $token) {
			return $token->isTokenValid($fieldValue);
		}
		
		protected function validation_captcha($fieldValue, Security\Captcha $captcha) {
			return $captcha->isCaptchaValid($fieldValue);
		}
		
		/**
		* Check if fieldValue is in the given list of value (param 1)
		*/
		protected function validation_inList($fieldValue, $array) {
			return Validator::isInList($fieldValue, $array);
		}
		
		protected function validation_mustNotBeSet($fieldValue) {
			return false;
		}
		
		protected function validation_regex($fieldValue, $regex) {
			if (!is_string($fieldValue)) {
				return false;
			}
		
			if(preg_match($regex, $fieldValue))
				return true;
			else
				return false;
		}
		
		protected function validation_range($fieldValue, $min, $max) {
			return Validator::isInRange($fieldValue, $min, $max);
		}
		
		protected function validation_rangeOut($fieldValue, $min, $max) {
			return Validator::isNotInRange($fieldValue, $min, $max);
		}
		
		protected function validation_maxLength($fieldValue, $max) {
			return Validator::maxLength($fieldValue, $max);
		}
			
		protected function validation_minLength($fieldValue, $min) {
			return Validator::minLength($fieldValue, $min);
		}
		
		protected function validation_lengthRange($fieldValue, $min, $max) {
			return Validator::lengthRange($fieldValue, $min, $max);
		}
		
		protected function validation_date($fieldValue, $format) {
			return Validator::date($fieldValue, $format);
		}
		
		/**
		* An array of primivite (not array) values
		*/
		protected function validation_simpleArray($fieldValue) {
			return Validator::isSimpleArray($fieldValue);
		}
		
		/**
		* Test if the value is a sub path with no ".."
		* Start and end slash "/" are not accepted
		* The first argument set if the folder "." is accepted or not (default: false (not))
		* valid example:
		* - a/b/c
		* - a/b/c.d (if point option set to true)
		* invalid example:
		* - /a/b/c
		* - a/b/c/
		* - a/b/c.d (if point option set to false)
		*/
		protected function validation_downPath($fieldValue) {
			return Validator::isDownPath($fieldValue);
		}
		
		/**
		* Test if file exists in a dir
		* params: 
		* - Folder in which file should be
		* - Accept extensions for the filename (default: null which means "all extensions")
		* - Recursive (true) or not (false)
		* ex: 
		* addValidation('avatar', 'fileExists', $errorMsgs['avatarNotExists'], array(IMG_DIR.'/avatars', array('png', 'jpeg', 'jpg', 'gif'), true));
		*/
		protected function validation_fileExists($fieldValue, $dir, $acceptExts = null, $recursion = false) {
			$fieldValue = (is_array($fieldValue)?null:$fieldValue);
		
			$isSubPath = $this->validation_downPath($fieldValue, array());
			$ext = substr($fieldValue,strrpos($fieldValue,'.') + 1);
			
			$exists = ($isSubPath AND 
				(is_null($acceptExts) OR in_array($ext, $acceptExts)) AND 
				($recursion OR strpos($fieldValue, '/') === false) AND 
				is_file($dir.'/'.$fieldValue));
				
			return $exists;
		}
		
		# FILES
		
		protected function validation_file_required($file) {
			return !($file == null OR $file['name'] == '');
		}
		
		protected function validation_file_noError($file) {
			return ($file['error'] == 0);
		}
		
		/**
		* Size in ko (first param)
		*/
		protected function validation_file_maxSize($file, $size) {
			return !($file['size'] > $size);
		}
		
		/**
		* If there is only one parameter, min and max are the same (it test equality)
		*/
		protected function validation_file_imgWidthRange($file, $min, $max = null) {
			if(is_file($file['tmp_name'])) {
				$imgInfos = getimagesize($file['tmp_name']);
				
				$max = ($max)?$max:$min;
				
				if($imgInfos) {
					return ($imgInfos[0] >= $min AND $imgInfos[0] <= $max);
				}
				else
					return false;
			}
			else {
				return false;
			}
		}
		
		protected function validation_file_imgHeightRange($file, $min, $max = null) {
			if(is_file($file['tmp_name'])) {
				$imgInfos = getimagesize($file['tmp_name']);
				
				$max = ($max)?$max:$min;
				
				if($imgInfos) {
					return ($imgInfos[1] >= $min AND $imgInfos[1] <= $max);
				}
				else
					return false;
			}
			else {
				return false;
			}
		}
		
		/**
		* Test if the file has an extension from given (first param array)
		* Extensions are without the point like "png"
		*/
		protected function validation_file_acceptedExtensions($file, $acceptedExts) {
			$ext = strtolower(substr($file['name'], strrpos($file['name'], '.') + 1));
			
			if(in_array($ext, array_map('strtolower', $acceptedExts))) {
				return true;
			}
			else
				return false;
		}
		
	}


/* require 'FormValidation.class.php';

$userFormValidator = new FormValidation();

$userFormValidator
->addValidation('pseudo' , 'alpha'    ,'Le pseudo ne peut contenir que des lettres !')
->addValidation('pseudo' , 'required')

->addValidation('email', 'required')
->addValidation('email', 'email')

->addValidation('ddn', 'lessThan' , null, '01012010')

->addValidation('password', 'required' , null)
->addValidation('password', 'sameAs' , null, 'passwordConf')

->addValidation('reglement', 'required','Vous devez accepter le reglement !')
->addValidation('chkde', 'mustNotExists','Non vous ne pouvez pas cocher cette case !')
;

$userFormerrors = $userFormValidator->isFormValid(true);
$userFormValidator->seterrorsDisplay('<p style="float: right; clear: right;">','</p>',''); */

?>